"""
重试退避机制
实现带指数退避的重试函数和相关工具函数
"""
import random
import time
import logging
from typing import Callable, Any, Optional, Type, TypeVar

from .strategies import RetryConfig

# 设置日志
logger = logging.getLogger("connllm.retry")

# 定义泛型返回类型
T = TypeVar('T')

def get_retry_delay(attempt: int, error: Exception, config: RetryConfig) -> float:
	"""
	计算重试延迟时间
	
	Args:
		attempt: 当前尝试次数(从0开始)
		error: 发生的错误
		config: 重试配置
		
	Returns:
		重试延迟时间(秒)
	"""
	# 如果是速率限制错误，并且有指定的重试时间
	from ...core.exceptions import RateLimitError
	if isinstance(error, RateLimitError) and error.retry_after:
		return error.retry_after
		
	# 计算指数退避延迟
	delay = min(config.max_delay, config.base_delay * (2 ** attempt))
	
	# 添加随机抖动 (0.8~1.2倍)
	if config.jitter:
		delay *= 0.8 + 0.4 * random.random()
		
	return delay

def should_retry(attempt: int, error: Exception, config: RetryConfig) -> bool:
	"""
	判断是否应该重试
	
	Args:
		attempt: 当前尝试次数(从0开始)
		error: 发生的错误
		config: 重试配置
		
	Returns:
		是否应该重试
	"""
	# 超过最大重试次数，不再重试
	if attempt >= config.max_retries:
		return False
		
	# 特定类型错误不重试
	from ...core.exceptions import (
		AuthenticationError, ConfigurationError, 
		QuotaExceededError, InvalidRequestError
	)
	
	no_retry_error_names = (
		"AuthenticationError", "ConfigurationError", 
		"QuotaExceededError", "InvalidRequestError",
		"ValueError", "TypeError"
	)
	
	if (isinstance(error, (AuthenticationError, ConfigurationError, 
						  QuotaExceededError, InvalidRequestError,
						  ValueError, TypeError)) or
		error.__class__.__name__ in no_retry_error_names):
		return False
		
	return True

def retry_with_backoff(
	func: Callable[..., T], 
	provider_type: str,
	model: Optional[str] = None, 
	error_type: Optional[Type[Exception]] = None,
	*args, **kwargs
) -> T:
	"""
	带指数退避的重试函数
	
	Args:
		func: 要调用的函数
		provider_type: 提供商类型
		model: 模型名称
		error_type: 错误类型，用于定制重试策略
		*args: 函数位置参数
		**kwargs: 函数关键字参数
		
	Returns:
		函数返回值
		
	Raises:
		Exception: 如果重试次数用尽仍然失败
	"""
	from ...core.exceptions import LLMException, map_provider_error
	
	# 创建重试配置
	config = RetryConfig.for_provider(provider_type, error_type)
	
	last_error = None
	for attempt in range(config.max_retries + 1):
		try:
			return func(*args, **kwargs)
		except Exception as e:
			# 将原始错误转换为标准LLM异常
			if not isinstance(e, LLMException):
				error = map_provider_error(provider_type, e, model)
			else:
				error = e
			
			last_error = error
			
			# 获取错误类型专用的重试策略（第一次失败后调整配置）
			if attempt == 0 and error_type is None:
				config = RetryConfig.for_provider(provider_type, type(error))
			
			# 判断是否应该重试
			if not should_retry(attempt, error, config):
				break
				
			# 获取重试延迟时间
			retry_delay = get_retry_delay(attempt, error, config)
			
			# 日志记录
			logger.warning(
				f"API调用失败({provider_type}/{model}), "
				f"重试 {attempt+1}/{config.max_retries}: {str(error)}, "
				f"等待 {retry_delay:.2f}秒"
			)
			
			time.sleep(retry_delay)
	
	# 所有重试都失败了
	if last_error:
		logger.error(f"所有重试都失败了({provider_type}/{model}): {str(last_error)}")
		raise last_error
	
	# 理论上不会到达这里，因为最后一次尝试后要么返回结果，要么抛出异常
	raise RuntimeError("重试逻辑错误：既没有返回结果也没有抛出异常")
